﻿//Выпадающий однострочный список с комментариями 
//при необходимости заменить на MultiLine ListBox http://www.codeproject.com/KB/combobox/nishmullinlisbox.aspx
//Добавлена возможность перехвата WIN32-сообщений скроллирования - http://www.codeproject.com/KB/combobox/ScrollingListbox.aspx
using System.Windows.Forms;
using System;
namespace Plugins.IntelliSense
{

    /// <summary>
    /// Реализация выпадающего списка с комментариями (ala Visual Studio)
    /// </summary>
    internal class DropDownList : System.Windows.Forms.ListBox
    {
        /// <summary>
        /// ToolTip комментариев
        /// </summary>
        private System.Windows.Forms.ToolTip toolTip;
        /// <summary>
        /// Позиция элемента списка, который отображается в данный момент в конце списка
        /// </summary>
        private int maxVisibleInx;
        /// <summary>
        /// Количество элементов списка отображаемых на экране
        /// </summary>
        private int visibleCount;
        /// <summary>
        /// Событие окончания скроллирования
        /// </summary>
        private event ScrollEventHandler Scrolled = null;
        /// <summary>
        /// Инициализация параметеров выпадающего списка
        /// </summary>
        public DropDownList()
        {
            toolTip = new System.Windows.Forms.ToolTip();

            // Set up the delays for the ToolTip.
            toolTip.AutoPopDelay = 5000;
            toolTip.InitialDelay = 3000;
            toolTip.ReshowDelay = 500;
            // Force the ToolTip text to be displayed whether or not the form is active.
            toolTip.ShowAlways = true;
            this.Width = 200;
            this.visibleCount = 7;
            this.Height = (visibleCount + 2) * this.ItemHeight;
            maxVisibleInx = visibleCount;

            this.Scrolled += new ScrollEventHandler(DropDownList_Scrolled);
        }

        

        /// <summary>
        /// Если пользователь перевел фокус на другой контрол, закрываем список
        /// </summary>
        /// <param name="e">-</param>
        protected override void OnLeave(System.EventArgs e)
        {
            base.OnLeave(e);
            if (this.Visible)
                this.Hide();
        }

        /// <summary>
        /// Если пользователь нажал Enter, Esc, Backspace, то закрываем данный контрол
        /// При Esc, Backspace - индекс выбранного пользователем элемента списка будет равен -1
        /// </summary>
        /// <param name="e">-</param>
        protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            base.OnKeyDown(e);

            if ((e.KeyCode == System.Windows.Forms.Keys.Escape) || (e.KeyCode == System.Windows.Forms.Keys.Back))
            {
                this.SelectedIndex = -1;
                toolTip.Hide(this);
                if (this.Visible)
                    this.Hide();
            }
            if (e.KeyCode == System.Windows.Forms.Keys.Enter)
            {
                toolTip.Hide(this);
                if (this.Visible)
                    this.Hide();
            }
        }

        /// <summary>
        /// Отображаем в нужном месте комментарии текущего выделенного элемента
        /// </summary>
        /// <param name="e">-</param>
        protected override void OnSelectedIndexChanged(System.EventArgs e)
        {
            if (this.SelectedIndex >= 0)
            {
                base.OnSelectedIndexChanged(e);
                toolTip.Hide(this);

                if (this.SelectedIndex > maxVisibleInx)
                    maxVisibleInx++;
                if (this.SelectedIndex < maxVisibleInx - visibleCount)
                    maxVisibleInx--;

                int relativeindx = visibleCount + this.SelectedIndex - maxVisibleInx;

                toolTip.Show("Комментарии к " + this.Items[SelectedIndex], this, this.Width, this.ItemHeight * relativeindx, 5000);
                toolTip.Active = true;
            }
        }
        /// <summary>
        /// Если пользователь выбрал элемент списка двойным кликом, закрываем список
        /// </summary>
        /// <param name="e">-</param>
        protected override void OnMouseDoubleClick(System.Windows.Forms.MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);
            if (this.Visible)
                this.Hide();
        }

        #region Win32-based scrolling
        /// <summary>
        /// Реализация цикла сообщений
        /// </summary>
        /// <param name="msg">-</param>
        protected override void WndProc(ref System.Windows.Forms.Message msg)
        {
            if (msg.Msg == WMVSCROLL)
            {
                if (Scrolled != null)
                {
                    ScrollInfo si = new ScrollInfo();
                    si.Mask = ScrollALL;
                    si.Size = System.Runtime.InteropServices.Marshal.SizeOf(si);
                    GetScrollInfo(msg.HWnd, SBVERT, ref si);

                    if (msg.WParam.ToInt32() == SBENDSCROLL)
                    {
                        ScrollEventArgs sargs = new ScrollEventArgs(
                            ScrollEventType.EndScroll,
                            si.Pos,
                            ScrollOrientation.VerticalScroll);
                        Scrolled(this, sargs);
                    }
                }
            }
            base.WndProc(ref msg);
        }

        /// <summary>
        /// Подписчик на событие окончания вертикального скроллирования
        /// </summary>
        /// <param name="sender">-</param>
        /// <param name="e">Параметры полосы-прокрутки</param>
        private void DropDownList_Scrolled(object sender, ScrollEventArgs e)
        {
            if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
            {
                maxVisibleInx = e.NewValue + visibleCount;
            }
        }

        /// <summary>
        /// Функция получения состояния полосы-прокрутки
        /// </summary>
        /// <param name="handle">хэндл контрола (см. парам "n")</param>
        /// <param name="n">Возможные значения: SBHORZ, SBVERT, SBCTL (см.ниже)</param>
        /// <param name="scrollInfo">Состояние полосы-прокрутки</param>
        /// <returns>-</returns>
        [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
        private static extern int GetScrollInfo(
            IntPtr handle, int n, ref ScrollInfo scrollInfo);

        /// <summary>
        /// The WM_HSCROLL message is sent to a window when a scroll event occurs in the window's standard horizontal scroll bar.
        /// </summary>
        private const int WMHSCROLL = 0x114;
        /// <summary>
        /// The WM_VSCROLL message is sent to a window when a scroll event occurs in the window's standard vertical scroll bar.
        /// </summary>
        private const int WMVSCROLL = 0x115;

        /// <summary>
        /// Подробное описание полей ниже - ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/shellcc/platform/commctls/scrollbars/aboutscrollbars.htm
        /// The user clicks the left scroll arrow.
        /// </summary>
        private const int SBLINELEFT = 0;
        /// <summary>
        /// The user clicks the right scroll arrow.
        /// </summary>
        private const int SBLINERIGHT = 1;
        /// <summary>
        /// The user clicks the scroll bar shaft to the left of the scroll box.
        /// </summary>
        private const int SBPAGELEFT = 2;
        /// <summary>
        /// The user clicks the scroll bar shaft to the right of the scroll box.
        /// </summary>
        private const int SBPAGERIGHT = 3;
        /// <summary>
        /// The user releases the scroll box after dragging it.
        /// </summary>
        private const int SBTHUMBPOSITION = 4;
        /// <summary>
        /// The user drags the scroll box.
        /// </summary>
        private const int SBTHUMBTRACK = 5;
        /// <summary>
        /// ??
        /// </summary>
        private const int SBLEFT = 6;
        /// <summary>
        /// ??
        /// </summary>
        private const int SBRIGHT = 7;
        /// <summary>
        /// The user releases the mouse after holding it on an arrow or in the scroll bar shaft. 
        /// </summary>
        private const int SBENDSCROLL = 8;

        /// <summary>
        /// Retrieves the parameters for the given window's standard horizontal scroll bar.  
        /// </summary>
        private const int  SBHORZ = 0;
        /// <summary>
        /// Retrieves the parameters for the given window's standard vertical scroll bar.  
        /// </summary>
        private const int  SBVERT = 1;
        /// <summary>
        /// Retrieves the parameters for a scroll bar control. The handle parameter must be the handle to the scroll bar control.  
        /// </summary>
        private const int  SBCTL = 2;
        /// <summary>
        /// Combination of the above flags
        /// </summary>
        private const int  SBBOTH = 3;
        /// <summary>
        /// Copies the current scroll box tracking position to the TrackPos member of the SCROLLINFO structure pointed to by lpsi. 
        /// </summary>
        private const int ScrollTRACKPOS = 0x10;
        /// <summary>
        /// Copies the scroll range to the MinPos and MaxPos members of the SCROLLINFO structure pointed to by lpsi. 
        /// </summary>
        private const int ScrollRANGE = 0x1;
        /// <summary>
        /// Copies the scroll position to the Pos member of the SCROLLINFO structure pointed to by lpsi. 
        /// </summary>
        private const int ScrollPOS = 0x4;
        /// <summary>
        /// Copies the scroll page to the PageSize member of the SCROLLINFO structure pointed to by lpsi. 
        /// </summary>
        private const int ScrollPAGE = 0x2;
        /// <summary>
        /// Combination of the above flags
        /// </summary>
        private const int ScrollALL = ScrollRANGE | ScrollPAGE | ScrollPOS | ScrollTRACKPOS;
        #endregion
    }
  
    /// <summary>
    /// WINAPI структура, описывающая состояние полосы-прокрутки
    /// </summary>
    internal struct ScrollInfo
    {
        /// <summary>
        ///  Specifies the size, in bytes, of this structure. The caller must set this to sizeof(SCROLLINFO). 
        /// </summary>
        public int Size;
        /// <summary>
        /// Specifies the scroll bar parameters to set or retrieve. This member can be a combination of the following values: 
        /// SIF_ALL - Combination of SIF_PAGE, SIF_POS, SIF_RANGE, and SIF_TRACKPOS.
        /// SIF_DISABLENOSCROLL - This value is used only when setting a scroll bar's parameters. If the scroll bar's new parameters make the scroll bar unnecessary, disable the scroll bar instead of removing it. 
        /// SIF_PAGE - The nPage member contains the page size for a proportional scroll bar.
        /// SIF_POS - The nPos member contains the scroll box position, which is not updated while the user drags the scroll box.
        /// SIF_RANGE - The nMin and nMax members contain the minimum and maximum values for the scrolling range.
        /// SIF_TRACKPOS - The nTrackPos member contains the current position of the scroll box while the user is dragging it.
        /// </summary>
        public int Mask;
        /// <summary>
        /// Specifies the minimum scrolling position. 
        /// </summary>
        public int MinPos;
        /// <summary>
        /// Specifies the maximum scrolling position. 
        /// </summary>
        public int MaxPos;
        /// <summary>
        /// Specifies the page size. A scroll bar uses this value to determine the appropriate size of the proportional scroll box. 
        /// </summary>
        public int PageSize;
        /// <summary>
        /// Specifies the position of the scroll box. 
        /// </summary>
        public int Pos;
        /// <summary>
        /// Specifies the immediate position of a scroll box that the user is dragging. 
        /// An application can retrieve this value while processing the SB_THUMBTRACK request code. 
        /// An application cannot set the immediate scroll position; the SetScrollInfo function ignores this member.
        /// </summary>
        public int TrackPos;
    }
}